#!/usr/bin/env python3

# Copyright (C) 2022-2023 Free Software Foundation, Inc.
# This file is part of GCC.

# GCC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.

# GCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3.  If not see
# <http://www.gnu.org/licenses/>.


# Generate gcc source files:
# - the export description of the standard C++ library module
# - the gperf tables used to hint at actions to fix problems
#   related to missing symbols in the std:: namespace

import csv
import os
import time

# The CSV file contains the following columns:
#  column  value
#       1  header file, including angle brackets
#       2  symbol name without std:: prefix
#       3  nonzero if to be exported
#       4  "no" if not to be added to the hint table else the appropriate enum cxx_dialect value
#       5  optional, a string used after #if in a line inserted first to enable conditional definitions


def print_condition(prev, e):
    if len(e) > 4 and e[4] != '':
        if not prev or prev != e[4]:
            if prev:
                print('#endif')
            print(f'#if {e[4]}')
        return e[4]
    if prev:
        print('#endif')
    return None


def export(script, content):
    print("""// This file is auto-generated by {:s}.
#if __cplusplus <= 202002L
# if __cplusplus == 202002L
#  ifdef __STRICT_ANSI__
#   error "module `std' is only available before C++23 if using -std=gnu++20"
#  endif
# else
#  error "module `std' is not available before C++23"
# endif
#endif

export module std;

import <bits/stdc++.h>;

// new/delete operators in global namespace from <new>
export using ::operator new;
export using ::operator delete;
export using ::operator new[];
export using ::operator delete[];""".format(script))
    header = ''
    printed_header = False
    cond = None
    for e in content:
        if e[0] != header:
            header = e[0]
            printed_header = False
        if e[2] != 0:
            if not printed_header:
                if cond:
                    print('#endif')
                    cond = None
                print(f'\n// {header}')
                printed_header = True
            cond = print_condition(cond, e)
            print(f'export using std::{e[1]};')
    if cond:
        print('#endif')


def hints(script, content):
    print("""%language=C++
%define class-name std_name_hint_lookup
%struct-type
%{{
/* This file is auto-generated by {:s}.  */
/* Copyright (C) {:s} Free Software Foundation, Inc.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */
%}}
struct std_name_hint
{{
  /* A name within "std::".  */
  const char *name;

  /* The header name defining it within the C++ Standard Library
     (with '<' and '>').  */
  const char* header;

  /* The dialect of C++ in which this was added.  */
  enum cxx_dialect min_dialect;
}};
%%
# The standard-defined types, functions, and options in the std:: namespace
# as defined in the C++ language specification.  The result is used in the
# get_std_name_hint functions.
#   throws an exception.
#""".format(script, time.strftime('%Y')))
    header = ''
    printed_header = False
    for e in content:
        if e[0] != header:
            header = e[0]
            printed_header = False
        if e[3] != 'no':
            if not printed_header:
                print(f'# {header}')
                printed_header = True
            print(f'{e[1]}, "{e[0]}", {e[3]}')


def remove_comment(f):
    for row in f:
        row = row.strip()
        if row[0] != '#':
            yield row


modes = {
    'export': export,
    'hints': hints
}


def usage(script):
    print(f'Usage: {script} [{"|".join(modes.keys())}] CSVFILE')
    exit(1)


def main(argv):
    if len(argv) < 3:
        usage(argv[0] if len(argv) > 0 else '???')

    script = argv[0]
    mode = argv[1]
    filename = argv[2]

    if mode not in modes:
        print(f"{script}: unrecognized mode '{mode}'")
        usage(script)

    try:
        with open(filename, 'r') as csvfile:
            modes[mode](os.path.basename(script), sorted(csv.reader(remove_comment(csvfile), delimiter=',')))
    except FileNotFoundError:
        print(f"{script}: cannot find CSV file '{filename}'")
        exit(1)
    except PermissionError:
        print(f"{script}: insufficient permission to read CSV file '{filename}'")
        exit(1)


if __name__ == '__main__':
    import sys
    main(sys.argv)
